package com.example.demoopcjava.opcua.demo2;

import org.eclipse.milo.opcua.sdk.client.OpcUaClient;
import org.eclipse.milo.opcua.sdk.client.api.subscriptions.UaMonitoredItem;
import org.eclipse.milo.opcua.sdk.client.api.subscriptions.UaSubscription;
import org.eclipse.milo.opcua.stack.core.AttributeId;
import org.eclipse.milo.opcua.stack.core.Identifiers;
import org.eclipse.milo.opcua.stack.core.types.builtin.DataValue;
import org.eclipse.milo.opcua.stack.core.types.builtin.NodeId;
import org.eclipse.milo.opcua.stack.core.types.builtin.StatusCode;
import org.eclipse.milo.opcua.stack.core.types.builtin.Variant;
import org.eclipse.milo.opcua.stack.core.types.enumerated.*;
import org.eclipse.milo.opcua.stack.core.types.structured.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;

import static com.google.common.collect.Lists.newArrayList;
import static org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.Unsigned.uint;
import static org.eclipse.milo.opcua.stack.core.util.ConversionUtil.toList;

/**
 * <b><code>OpcUaOperationSupport</code></b>
 * <p>
 * Description
 * <p>
 * <b>Create Time:</b> 2022/1/10 11:04.
 *
 * @author qd.yt
 * @version 0.0.1
 * @since demo-opc-java v0.0.1
 */
@Component
public class OpcUaOperationSupport {

    private final Logger logger = LoggerFactory.getLogger(getClass());
    /**
     * 浏览节点
     */
    public void browseNode() {
        OpcUaClient client = ClientGen.opcUaClient;
        try {
            client.connect().get();
            browseNode("", client, Identifiers.RootFolder);

        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }

    /**
     * 单个读取 PLC
     *
     * @param server
     * @return
     */
    public ResultBean readPLC(OpcModel server) {
        ResultBean resultBean = new ResultBean(false, "");
        String accessPath = server.getAccessPath();
        String itemName = server.getItemName();
        String item = accessPath + "." + itemName;
        OpcUaClient client = ClientGen.opcUaClient;
        try {
            client.connect().get();
            NodeId nodeId = new NodeId(2, item);
            CompletableFuture<DataValue> readValue = client.readValue(0.0, TimestampsToReturn.Both, nodeId);
            DataValue value = readValue.get();
            String plcValue = value.getValue().getValue().toString();
            String statusCode = value.getStatusCode().toString();
            if (value.getStatusCode() != null) {
                if (value.getStatusCode().isGood()) {
                    resultBean.setMsg("获取数据成功。");
                    resultBean.setResult(plcValue);
                    resultBean.setSuccess(true);
                } else {
                    resultBean.setMsg("获取数据失败。");
                    resultBean.setResult(statusCode);
                    resultBean.setSuccess(false);
                }
                logger.info("======== >  it means successfully read StatusCode = {}", statusCode);
            } else {
                resultBean.setMsg("获取数据出现异常。");
                resultBean.setResult("出错了。。。。");
                resultBean.setSuccess(false);
                logger.error("=====》  读数据异常，未获取到数据。。。");
            }

        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
        return resultBean;
    }

    /**
     * 写入 PLC
     * @param server
     * @param editValue
     * @return
     */
    public ResultBean writePLC(OpcModel server, String editValue) {
        ResultBean resultBean = new ResultBean(false, "");
        String accessPath = server.getAccessPath();
        String itemName = server.getItemName();
        String item = accessPath + "." + itemName;
        try {
//          创建连接
            OpcUaClient client = ClientGen.opcUaClient;
            client.connect().get();
//          创建节点
            NodeId nodeId = new NodeId(2, item);
//          创建Variant对象和DataValue对象
            Variant v;
            boolean b = true;
            switch (editValue) {
                case "1":
                    v = new Variant(b);
                    break;
                case "0":
                    b = false;
                    v = new Variant(b);
                    break;
                default:
                    v = new Variant(editValue);
                    break;
            }
            DataValue dv = new DataValue(v, null, null);
            StatusCode statusCode = client.writeValue(nodeId, dv).get();
            if (statusCode.isGood()) {
                resultBean.setMsg("写入数据成功。");
                resultBean.setSuccess(true);
                resultBean.setResult(editValue);
                logger.info("========== >  it means successfully Wrote '{}' to nodeId={}, statusCodes = {}", v, nodeId, statusCode.toString());
            } else {
                resultBean.setMsg("写入数据失败。item = " + item);
                resultBean.setSuccess(false);
                resultBean.setResult(statusCode.toString());
                logger.error("statusCodes:" + statusCode.toString());
            }

        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
        return resultBean;
    }

    /**
     * 订阅变量
     *
     * @param server
     * @return
     */
    public ResultBean createSubscription(OpcModel server) {
        ResultBean resultBean = new ResultBean(false, "");
        String accessPath = server.getAccessPath();
        String itemName = server.getItemName();
        String item = accessPath + "." + itemName;
        try {
            OpcUaClient client = ClientGen.opcUaClient;
            client.connect().get();

            //创建发布间隔1000ms的订阅对象
            UaSubscription subscription = client.getSubscriptionManager().createSubscription(1000.0).get();

            //创建监控的参数
            MonitoringParameters parameters = new MonitoringParameters(
                    uint(1),
                    // 发布间隔
                    1000.0,
                    // filter, 空表示用默认值
                    null,
                    // 队列大小
                    uint(10),
                    //放弃旧配置
                    true
            );
            //创建订阅的变量
            NodeId nodeId = new NodeId(2, item);
            ReadValueId readValueId = new ReadValueId(nodeId, AttributeId.Value.uid(), null, null);
            //创建监控项请求
            //该请求最后用于创建订阅。
            MonitoredItemCreateRequest request = new MonitoredItemCreateRequest(readValueId, MonitoringMode.Reporting, parameters);
            List<MonitoredItemCreateRequest> requests = new ArrayList<>();
            requests.add(request);
            //创建监控项，并且注册变量值改变时候的回调函数。
            UaSubscription.ItemCreationCallback onItemCreated =
                    (subscriptionItem, id) -> subscriptionItem.setValueConsumer((UaMonitoredItem item1, DataValue value) -> {
                                logger.info("===== >  here is callbacks ... 订阅的回调函数。 ");
                                resultBean.setMsg("订阅成功, item1 :" + item1.getReadValueId().getNodeId().toString());
                                resultBean.setResult(value.getValue().toString());
                                resultBean.setSuccess(true);
                                logger.info("subscription value received: item={}, value={}", item1.getReadValueId().getNodeId(), value.getValue());
                            }
                    );
            List<UaMonitoredItem> monitoredItems = subscription.createMonitoredItems(
                    TimestampsToReturn.Both,
                    newArrayList(request),
                    onItemCreated
            ).get();

            for (UaMonitoredItem monitoredItem : monitoredItems) {
                if (monitoredItem.getStatusCode().isGood()) {
                    logger.info("item created for nodeId={}", monitoredItem.getReadValueId().getNodeId());
                } else {
                    logger.warn(
                            "failed to create item for nodeId={} (status={})",
                            monitoredItem.getReadValueId().getNodeId(), monitoredItem.getStatusCode());
                }
            }
            return resultBean;
        } catch (Exception e) {
            logger.error("订阅变量失败");
            resultBean.setMsg(e.getMessage());
            resultBean.setResult("订阅变量进入异常。");
            resultBean.setSuccess(false);
        }
        return resultBean;
    }

    /**
     * 查看历史变量记录
     *
     * @param server
     * @param editValue
     * @return
     */
    public ResultBean historyRead(OpcModel server, String editValue) {
        return null;
    }

    /**
     * 浏览节点（抽取方法）
     *
     * @param indent
     * @param client
     * @param browseRoot
     */
    private void browseNode(String indent, OpcUaClient client, NodeId browseRoot) {
        BrowseDescription browse = new BrowseDescription(
                browseRoot,
                BrowseDirection.Forward,
                Identifiers.References,
                true,
                uint(NodeClass.Object.getValue() | NodeClass.Variable.getValue()),
                uint(BrowseResultMask.All.getValue())
        );
        try {
            BrowseResult browseResult = client.browse(browse).get();

            List<ReferenceDescription> references = toList(browseResult.getReferences());

            for (ReferenceDescription rd : references) {
                logger.info("{} Node={}", indent, rd.getBrowseName().getName());

                // recursively browse to children
                rd.getNodeId().toNodeId(client.getNamespaceTable())
                        .ifPresent(nodeId -> browseNode(indent + "  ", client, nodeId));
            }
        } catch (InterruptedException | ExecutionException e) {
            logger.error("Browsing nodeId={} failed: {}", browseRoot, e.getMessage(), e);
        }
    }
}